// 订阅者/观察者
class Watcher{
    /**
     * 
     * @param {*} vm 当前vm对象
     * @param {*} expr 取值表达式
     * @param {*} cb 回调函数，用于更新视图
     */
    constructor(vm,expr,cb){
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        // 获取旧值,并保存
        this.oldVal = this.getOldVal();
    }
    // 更新视图的函数
    update(){
        // 获取新值
       const newVal =  compileUtil.getVal(this.expr,this.vm);
        // 如果新值不等于旧值，利用回调将新值返回
        if(newVal !== this.oldVal){
            // 利用传入的回调，将新值返回
            this.cb(newVal);
        }
    }
    // 获取旧值
    getOldVal(){
        // 将当前的观察者挂载到订阅器中
        Dep.target = this;
       const oldVal =  compileUtil.getVal(this.expr,this.vm);
        // 销毁当前观察者
        Dep.target = null;   
        return oldVal;
    }
}

// 订阅器/依赖收集器
class Dep{
    constructor(){
        // 定义依赖容器
        this.subs = [];
    }
    // 收集观察者
    addSub(watcher){
        this.subs.push(watcher);
    }
    // 通知观察者去更新视图
    notify(){
        // 遍历容器，找到对应观察者，调用更新方法去更新视图
        this.subs.forEach(watcher => watcher.update())  
    }
}
// 数据观察者类，使用Object.defineProperty实现数据劫持
class Observer{
    constructor(data){
        this.observe(data)
    }
    // 劫持函数
    observe(data){
        // 此处仅对对象做数据观测
        if(data && typeof data === 'object'){
            // 遍历获取到到所有的key
            Object.keys(data).forEach(key => {
                // 使用监听函数进行数据监听
                this.defineReactive(data,key,data[key])
            })
        }
    }
    // 监听函数
    defineReactive(obj,key,value){
        // 传入的value是对象的一个属性值，属性值也可能是一个对象，所以需要递归遍历
        this.observe(value);
        // 创建依赖收集器
        const dep = new Dep();
        // 劫持所有的属性
        Object.defineProperty(obj,key,{
            // 是否可枚举
            enumerable:true,
            // 是否可更改
            configurable:false,
            // 获取数据进行初始化
            get(){
                // 订阅数据变化时，向订阅器Dep中添加观察者，用于收集属性的依赖，实现一个属性对应一个观察者
                Dep.target && dep.addSub(Dep.target)
                return value;
            },
            // 使用箭头函数是为了将函数内部的this指向外部的Observer类
            set:(newVal) => {
                // 获取到新的值时，初始状态是不会对该值进行劫持的，所以要对新值做劫持操作
                this.observe(newVal)
                // 当前传入的新值不等于旧值
                if(newVal !== value){
                    value = newVal;
                }
                // 更新数据之后，告诉Dep去通知变化
                dep.notify();
            }
        })
    }
}